/*
 * Id.java
 *
 * Created on August 11, 2005, 9:54 PM
 *
 * To change this basis, choose Tools | Options and locate the basis under
 * the Source Creation and Management node. Right-click the basis and choose
 * Open. You can then make changes to the basis in the Source Editor.
 */

package com.xucia.jsponic.data;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

import com.xucia.jsponic.JsponicRuntime;



/**
 *
 * @author Kris Zyp
 * This class represents a select query
 */
public class JsonPath extends Identification<List> {
	@Override
	protected List resolveTarget() {
		throw new RuntimeException("getTarget should always be used for JsonPath");
	}
	//	public Stipulation stipulation;
//	public String constructionString;
	Object[] parameters;
	int param;
	String jsonPath;
	public static synchronized JsonPath idForString(String query) {
		JsonPath objId = new JsonPath();
		String[] parts;
		parts = query.split("/",2);
		if (parts.length < 2){
			throw new RuntimeException("All ids must be in form of datasource/objectid");
			//objId.source = DataSourceManager.getRootSource();
			//objId.subObjectId = value;
		}
		else
		{
			objId.source = DataSourceManager.getSource(parts[0]);
			objId.subObjectId= parts[1].split("[\\.\\[].*")[0];
			objId.jsonPath = "$" + parts[1].substring(objId.subObjectId.length());
			parts = objId.jsonPath.split("#");
			if (parts.length > 1) {
				boolean first = true;
				objId.jsonPath = "";
				int i = 0;
				for (String part : parts) {
					objId.jsonPath += (first ? "" : ("__param" + i++)) + part; 
					first = false;
				}
			}

		}
		return objId;
	}
	static Function jsonPathFunction; 
	static Scriptable queryScope = Context.enter().initStandardObjects();
	static {
		InputStream is = JsonPath.class.getClassLoader().getResourceAsStream("jsonpath.js");
		try {
			Context.enter().evaluateString(queryScope, getResourceAsString(is), "jsonpath.js", 1, null);
			jsonPathFunction = (Function) queryScope.get("jsonPath", queryScope);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	public static Object query(String jsonPath, Object... parameters) {
		JsonPath query = new JsonPath();
		query.setParameters(parameters);
		query.jsonPath = jsonPath;
		//return PersistableImpl.mapPersistent(query);
		return query.getTarget();
	}
	
	@Override
	public synchronized List getTarget() {
		return doJsonPath(jsonPath);
	}
	public boolean isDefinite() {
		return isDefinite(jsonPath);
	}
	static boolean isDefinite(String jsonPath) {
		return !jsonPath.replaceAll("\"[^\"\\\\\\n\r]*\"", "").matches(".*(\\.\\.|\\*|\\?|,|:).*");
	}
	private List doJsonPath(String jsonPath) {
		return doJsonPath(ObjectId.idForObject(source,subObjectId).getTarget(),jsonPath);
	}
	private List doJsonPath(Persistable target, String jsonPath) {
		Scriptable paramScope = new NativeObject();
		if (parameters != null)
			for (int i = 0; i < parameters.length; i++) {
				paramScope.put("__param" + i, paramScope, parameters[i]);
			}
		paramScope.setParentScope(queryScope);		
		Object result = jsonPathFunction.call(Context.enter(), paramScope, queryScope, new Object[]{target,jsonPath});
		if (result instanceof Boolean)
			return JsponicRuntime.newArray();
		((NativeArray)result).setPrototype(ScriptableObject.getClassPrototype(GlobalData.getGlobalScope(),"Array"));
		return (List) result; 

	}
	public class PropertyPair {
		Persistable target;
		Object key;
		PropertyPair(Persistable target, Object key) {
			super();
			this.target = target;
			this.key = key;
		}
		public Persistable getTarget() {
			return target;
		}
		public Object getKey() {
			return key;
		}
	}
	public class PropertyValuePair extends PropertyPair {

		public PropertyValuePair(Persistable target, Object key) {
			super(target, key);
			// TODO Auto-generated constructor stub
		}
	}
	public List<PropertyPair> getPropertyPairs() {
		int lastDot = jsonPath.lastIndexOf('.');
		int firstBracket = jsonPath.indexOf('[');
		String propertyPart;
		PersistableList<Persistable> firstLevel;
		if (firstBracket != -1 && (firstBracket < lastDot || lastDot == -1)) {
			firstLevel = (PersistableList) doJsonPath(jsonPath.substring(0,firstBracket));
			propertyPart = jsonPath.substring(firstBracket);
		}
		else if (lastDot == -1)
			throw new RuntimeException("No property was provided");
		else {
			firstLevel = (PersistableList) doJsonPath(jsonPath.substring(0,lastDot));
			propertyPart = jsonPath.substring(lastDot);
		}
		List<PropertyPair> pairs = new ArrayList();
		for (Persistable obj : firstLevel) {
			if (isDefinite(propertyPart))
				pairs.add(new PropertyPair(obj,propertyPart));
			else {
				PersistableList secondLevel = (PersistableList) doJsonPath(obj,"$" + propertyPart);
				for (Object value : secondLevel) 
					pairs.add(new PropertyValuePair(obj,value));
			}
		}
		return pairs;
	}
	private static String getResourceAsString(InputStream is) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(is));
		StringBuffer buffer = new StringBuffer();
		String line;
		while ((line = in.readLine()) != null) {
			buffer.append(line);
			buffer.append("\n");
		}
		return buffer.toString();
	}

	public Object[] getParameters() {
		return parameters;
	}

	public void setParameters(Object[] parameters) {
		this.parameters = parameters;
	}

}
